﻿namespace Smark.Core
{
    using System;
    using System.Collections.Generic;
    using System.Reflection;
    using System.Reflection.Emit;

    public class ReflectionHandlerFactory
    {
        private static Dictionary<FieldInfo, GetValueHandler> mFieldGetHandlers = new Dictionary<FieldInfo, GetValueHandler>();
        private static Dictionary<FieldInfo, SetValueHandler> mFieldSetHandlers = new Dictionary<FieldInfo, SetValueHandler>();
        private static Dictionary<Type, ObjectInstanceHandler> mInstanceHandlers = new Dictionary<Type, ObjectInstanceHandler>();
        private static Dictionary<MethodInfo, FastMethodHandler> mMethodHandlers = new Dictionary<MethodInfo, FastMethodHandler>();
        private static Dictionary<PropertyInfo, GetValueHandler> mPropertyGetHandlers = new Dictionary<PropertyInfo, GetValueHandler>();
        private static Dictionary<PropertyInfo, SetValueHandler> mPropertySetHandlers = new Dictionary<PropertyInfo, SetValueHandler>();

        private static GetValueHandler CreateFieldGetHandler(FieldInfo field)
        {
            DynamicMethod method = new DynamicMethod("", typeof(object), new Type[] { typeof(object) }, field.DeclaringType);
            ILGenerator iLGenerator = method.GetILGenerator();
            iLGenerator.Emit(OpCodes.Ldarg_0);
            iLGenerator.Emit(OpCodes.Ldfld, field);
            EmitBoxIfNeeded(iLGenerator, field.FieldType);
            iLGenerator.Emit(OpCodes.Ret);
            return (GetValueHandler) method.CreateDelegate(typeof(GetValueHandler));
        }

        private static SetValueHandler CreateFieldSetHandler(FieldInfo field)
        {
            DynamicMethod method = new DynamicMethod("", null, new Type[] { typeof(object), typeof(object) }, field.DeclaringType);
            ILGenerator iLGenerator = method.GetILGenerator();
            iLGenerator.Emit(OpCodes.Ldarg_0);
            iLGenerator.Emit(OpCodes.Ldarg_1);
            EmitCastToReference(iLGenerator, field.FieldType);
            iLGenerator.Emit(OpCodes.Stfld, field);
            iLGenerator.Emit(OpCodes.Ret);
            return (SetValueHandler) method.CreateDelegate(typeof(SetValueHandler));
        }

        private static ObjectInstanceHandler CreateInstanceHandler(Type type)
        {
            DynamicMethod method = new DynamicMethod(string.Empty, type, null, type.Module);
            ILGenerator iLGenerator = method.GetILGenerator();
            iLGenerator.DeclareLocal(type, true);
            iLGenerator.Emit(OpCodes.Newobj, type.GetConstructor(new Type[0]));
            iLGenerator.Emit(OpCodes.Stloc_0);
            iLGenerator.Emit(OpCodes.Ldloc_0);
            iLGenerator.Emit(OpCodes.Ret);
            return (ObjectInstanceHandler) method.CreateDelegate(typeof(ObjectInstanceHandler));
        }

        private static FastMethodHandler CreateMethodHandler(MethodInfo methodInfo)
        {
            DynamicMethod method = new DynamicMethod(string.Empty, typeof(object), new Type[] { typeof(object), typeof(object[]) }, methodInfo.DeclaringType.Module);
            ILGenerator iLGenerator = method.GetILGenerator();
            ParameterInfo[] parameters = methodInfo.GetParameters();
            Type[] typeArray = new Type[parameters.Length];
            for (int i = 0; i < typeArray.Length; i++)
            {
                if (parameters[i].ParameterType.IsByRef)
                {
                    typeArray[i] = parameters[i].ParameterType.GetElementType();
                }
                else
                {
                    typeArray[i] = parameters[i].ParameterType;
                }
            }
            LocalBuilder[] builderArray = new LocalBuilder[typeArray.Length];
            for (int j = 0; j < typeArray.Length; j++)
            {
                builderArray[j] = iLGenerator.DeclareLocal(typeArray[j], true);
            }
            for (int k = 0; k < typeArray.Length; k++)
            {
                iLGenerator.Emit(OpCodes.Ldarg_1);
                EmitFastInt(iLGenerator, k);
                iLGenerator.Emit(OpCodes.Ldelem_Ref);
                EmitCastToReference(iLGenerator, typeArray[k]);
                iLGenerator.Emit(OpCodes.Stloc, builderArray[k]);
            }
            if (!methodInfo.IsStatic)
            {
                iLGenerator.Emit(OpCodes.Ldarg_0);
            }
            for (int m = 0; m < typeArray.Length; m++)
            {
                if (parameters[m].ParameterType.IsByRef)
                {
                    iLGenerator.Emit(OpCodes.Ldloca_S, builderArray[m]);
                }
                else
                {
                    iLGenerator.Emit(OpCodes.Ldloc, builderArray[m]);
                }
            }
            if (methodInfo.IsStatic)
            {
                iLGenerator.EmitCall(OpCodes.Call, methodInfo, null);
            }
            else
            {
                iLGenerator.EmitCall(OpCodes.Callvirt, methodInfo, null);
            }
            if (methodInfo.ReturnType == typeof(void))
            {
                iLGenerator.Emit(OpCodes.Ldnull);
            }
            else
            {
                EmitBoxIfNeeded(iLGenerator, methodInfo.ReturnType);
            }
            for (int n = 0; n < typeArray.Length; n++)
            {
                if (parameters[n].ParameterType.IsByRef)
                {
                    iLGenerator.Emit(OpCodes.Ldarg_1);
                    EmitFastInt(iLGenerator, n);
                    iLGenerator.Emit(OpCodes.Ldloc, builderArray[n]);
                    if (builderArray[n].LocalType.IsValueType)
                    {
                        iLGenerator.Emit(OpCodes.Box, builderArray[n].LocalType);
                    }
                    iLGenerator.Emit(OpCodes.Stelem_Ref);
                }
            }
            iLGenerator.Emit(OpCodes.Ret);
            return (FastMethodHandler) method.CreateDelegate(typeof(FastMethodHandler));
        }

        private static GetValueHandler CreatePropertyGetHandler(PropertyInfo property)
        {
            DynamicMethod method = new DynamicMethod(string.Empty, typeof(object), new Type[] { typeof(object) }, property.DeclaringType.Module);
            ILGenerator iLGenerator = method.GetILGenerator();
            iLGenerator.Emit(OpCodes.Ldarg_0);
            iLGenerator.EmitCall(OpCodes.Callvirt, property.GetGetMethod(), null);
            EmitBoxIfNeeded(iLGenerator, property.PropertyType);
            iLGenerator.Emit(OpCodes.Ret);
            return (GetValueHandler) method.CreateDelegate(typeof(GetValueHandler));
        }

        private static SetValueHandler CreatePropertySetHandler(PropertyInfo property)
        {
            DynamicMethod method = new DynamicMethod(string.Empty, null, new Type[] { typeof(object), typeof(object) }, property.DeclaringType.Module);
            ILGenerator iLGenerator = method.GetILGenerator();
            iLGenerator.Emit(OpCodes.Ldarg_0);
            iLGenerator.Emit(OpCodes.Ldarg_1);
            EmitCastToReference(iLGenerator, property.PropertyType);
            iLGenerator.EmitCall(OpCodes.Callvirt, property.GetSetMethod(), null);
            iLGenerator.Emit(OpCodes.Ret);
            return (SetValueHandler) method.CreateDelegate(typeof(SetValueHandler));
        }

        private static void EmitBoxIfNeeded(ILGenerator il, Type type)
        {
            if (type.IsValueType)
            {
                il.Emit(OpCodes.Box, type);
            }
        }

        private static void EmitCastToReference(ILGenerator il, Type type)
        {
            if (type.IsValueType)
            {
                il.Emit(OpCodes.Unbox_Any, type);
            }
            else
            {
                il.Emit(OpCodes.Castclass, type);
            }
        }

        private static void EmitFastInt(ILGenerator il, int value)
        {
            switch (value)
            {
                case -1:
                    il.Emit(OpCodes.Ldc_I4_M1);
                    return;

                case 0:
                    il.Emit(OpCodes.Ldc_I4_0);
                    return;

                case 1:
                    il.Emit(OpCodes.Ldc_I4_1);
                    return;

                case 2:
                    il.Emit(OpCodes.Ldc_I4_2);
                    return;

                case 3:
                    il.Emit(OpCodes.Ldc_I4_3);
                    return;

                case 4:
                    il.Emit(OpCodes.Ldc_I4_4);
                    return;

                case 5:
                    il.Emit(OpCodes.Ldc_I4_5);
                    return;

                case 6:
                    il.Emit(OpCodes.Ldc_I4_6);
                    return;

                case 7:
                    il.Emit(OpCodes.Ldc_I4_7);
                    return;

                case 8:
                    il.Emit(OpCodes.Ldc_I4_8);
                    return;
            }
            if ((value > -129) && (value < 0x80))
            {
                il.Emit(OpCodes.Ldc_I4_S, (sbyte) value);
            }
            else
            {
                il.Emit(OpCodes.Ldc_I4, value);
            }
        }

        public static GetValueHandler FieldGetHandler(FieldInfo field)
        {
            GetValueHandler handler;
            if (mFieldGetHandlers.ContainsKey(field))
            {
                return mFieldGetHandlers[field];
            }
            lock (typeof(ReflectionHandlerFactory))
            {
                if (mFieldGetHandlers.ContainsKey(field))
                {
                    return mFieldGetHandlers[field];
                }
                handler = CreateFieldGetHandler(field);
                mFieldGetHandlers.Add(field, handler);
            }
            return handler;
        }

        public static SetValueHandler FieldSetHandler(FieldInfo field)
        {
            SetValueHandler handler;
            if (mFieldSetHandlers.ContainsKey(field))
            {
                return mFieldSetHandlers[field];
            }
            lock (typeof(ReflectionHandlerFactory))
            {
                if (mFieldSetHandlers.ContainsKey(field))
                {
                    return mFieldSetHandlers[field];
                }
                handler = CreateFieldSetHandler(field);
                mFieldSetHandlers.Add(field, handler);
            }
            return handler;
        }

        public static ObjectInstanceHandler InstanceHandler(Type type)
        {
            ObjectInstanceHandler handler;
            if (mInstanceHandlers.ContainsKey(type))
            {
                return mInstanceHandlers[type];
            }
            lock (typeof(ReflectionHandlerFactory))
            {
                if (mInstanceHandlers.ContainsKey(type))
                {
                    return mInstanceHandlers[type];
                }
                handler = CreateInstanceHandler(type);
                mInstanceHandlers.Add(type, handler);
            }
            return handler;
        }

        public static FastMethodHandler MethodHandler(MethodInfo method)
        {
            FastMethodHandler handler = null;
            if (mMethodHandlers.ContainsKey(method))
            {
                return mMethodHandlers[method];
            }
            lock (typeof(ReflectionHandlerFactory))
            {
                if (mMethodHandlers.ContainsKey(method))
                {
                    return mMethodHandlers[method];
                }
                handler = CreateMethodHandler(method);
                mMethodHandlers.Add(method, handler);
            }
            return handler;
        }

        public static GetValueHandler PropertyGetHandler(PropertyInfo property)
        {
            GetValueHandler handler;
            if (mPropertyGetHandlers.ContainsKey(property))
            {
                return mPropertyGetHandlers[property];
            }
            lock (typeof(ReflectionHandlerFactory))
            {
                if (mPropertyGetHandlers.ContainsKey(property))
                {
                    return mPropertyGetHandlers[property];
                }
                handler = CreatePropertyGetHandler(property);
                mPropertyGetHandlers.Add(property, handler);
            }
            return handler;
        }

        public static SetValueHandler PropertySetHandler(PropertyInfo property)
        {
            SetValueHandler handler;
            if (mPropertySetHandlers.ContainsKey(property))
            {
                return mPropertySetHandlers[property];
            }
            lock (typeof(ReflectionHandlerFactory))
            {
                if (mPropertySetHandlers.ContainsKey(property))
                {
                    return mPropertySetHandlers[property];
                }
                handler = CreatePropertySetHandler(property);
                mPropertySetHandlers.Add(property, handler);
            }
            return handler;
        }
    }
}

